#include "mem_block.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

extern void *__libc_malloc(size_t size);
extern void __libc_free(void *ptr);

/**
 * @description: Invoke clib free.
 * @param {void} *ptr
 * @return {*}
 */
void clib_free(void *ptr)
{
    __libc_free(ptr);
}

/**
 * @description: Invoke clib malloc.
 * @param {size_t} size
 * @return {*}
 */
void *clib_malloc(size_t size)
{
    return __libc_malloc(size);
}

/**
 * @description: Get memory from clib, if memory pool exhausted.
 * @param {size_t} size
 * @return {*}
 */
void *get_mem_from_clib(size_t size)
{
    mem_unit_t *p = (mem_unit_t *)clib_malloc(sizeof(mem_unit_t) + size);
    p->info.belongs = 0;
    p->info.unit_size = size;
    p->ptr = mem_unit_to_ptr(p);

    return p->ptr;
}

/**
 * @description: Release a thread cache's chunk to system.
 * @param {st_chunk} *chunk
 * @return {*}
 */
void release_tcache_chunk(struct st_chunk **chunk)
{
    MP_ASSERT(chunk != NULL);

    munmap((*chunk)->buffer, (*chunk)->buffer_size);
    clib_free(*chunk);
    *chunk = NULL;
}

/**
 * @description: Allocate thread cache's chunk from system.
 * @param {st_chunk} **chunk
 * @param {size_t} buffer_size
 * @return {*}
 */
void alloc_tcache_chunk(struct st_chunk **chunk, size_t buffer_size)
{
    if (chunk != NULL) {
        (*chunk) = clib_malloc(sizeof(struct st_chunk));
        (*chunk)->buffer = mmap(0, buffer_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        (*chunk)->buffer_size = buffer_size;
        (*chunk)->offset = 0;
    }
}

/**
 * @description: Allocate thread cache's memory blocks.
 * @param {U32} block_cnt
 * @return {*}
 */
mem_block_info_t **mem_block_init(U32 block_cnt)
{
    U32 i;

    mem_block_info_t **mem_block = (mem_block_info_t **)clib_malloc(block_cnt * sizeof(mem_block_info_t *));
    MP_ASSERT(mem_block != NULL);

    memset(mem_block, 0, block_cnt * sizeof(mem_block_info_t *));
    for (i = 0; i < block_cnt; i++) {
        mem_block[i] = (mem_block_info_t *)clib_malloc(sizeof(mem_block_info_t));

        mem_block[i]->mem_units = NULL;
    }

    return mem_block;
}

/**
 * @description: Initialization a memory unit.
 * @param {mem_unit_t} *mu
 * @param {size_t} unit_size
 * @return {*}
 */
static __inline__ void mem_unit_init(mem_unit_t *mu, size_t unit_size)
{
    MP_ASSERT(mu != NULL);

    mu->ptr = mem_unit_to_ptr(mu);
    mu->info.is_free = 1;
    mu->info.belongs = 1;
    mu->info.unit_size = unit_size;
    mu->next = 0;
}

/**
 * @description: Allocate a memory unit and initialization it.
 * @param {st_chunk} *chunk
 * @param {size_t} unit_size
 * @return {*} Pointer to mem_unit_t *, or NULL if chunk is exhausted.
 */
static mem_unit_t *alloc_mem_unit(struct st_chunk *chunk, size_t unit_size)
{
    mem_unit_t *mu = NULL;
    MP_ASSERT(chunk != NULL);

    if (chunk->offset + sizeof(mem_unit_t) + unit_size <= chunk->buffer_size) {
        mu = (mem_unit_t *)((uintptr_t)chunk->buffer + chunk->offset);
        chunk->offset += sizeof(mem_unit_t) + unit_size;
        mem_unit_init(mu, unit_size);
    }

    return mu;
}

#define OFFSET_OF_BLOCK_CACHE(mem_unit_addr, chunk_buffer) \
        ((uintptr_t)(mem_unit_addr) - (uintptr_t)(chunk_buffer))

#define OFFSET_TO_MEM_UNIT(offset, chunk_buffer) \
        (mem_unit_t *)((uintptr_t)(chunk_buffer) + (offset))

/**
 * @description: Get memory from chunk, and saved to specific memory block.
 * @param {st_chunk} *chunk
 * @param {mem_block_info_t} *mem_block
 * @param {size_t} size
 * @return {*}
 */
void *get_mem_from_block(struct st_chunk *chunk, mem_block_info_t *mem_block, size_t size)
{
    if (mem_block->mem_units == NULL) {
        mem_block->mem_units = alloc_mem_unit(chunk, size);
        if (mem_block->mem_units == NULL) {
            goto out_clib_malloc;
        }
        mem_block->mem_units->info.is_free = 0;

        return mem_block->mem_units->ptr;
    } else if (mem_block->mem_units->info.unit_size >= size && mem_block->mem_units->info.is_free == 1) {
        mem_block->mem_units->info.is_free = 0;
        return mem_block->mem_units->ptr;
    }

    mem_unit_t *unit = mem_block->mem_units;
    while (1) {
        if (unit->next == 0) {
            mem_unit_t *new = alloc_mem_unit(chunk, size);
            if (unlikely(new == NULL)) {
                goto out_clib_malloc;
            }

            new->info.is_free = 0;
            unit->next = OFFSET_OF_BLOCK_CACHE(new, chunk->buffer);

            return new->ptr;
        }

        mem_unit_t *next = OFFSET_TO_MEM_UNIT(unit->next, chunk->buffer);
        if (next->info.is_free == 1 && next->info.unit_size >= size) {
            next->info.is_free = 0;
            return next->ptr;
        }

        unit = next;
    }

out_clib_malloc:
    return get_mem_from_clib(size);
}

/**
 * @description: Get block use status.
 * @param {void} *base_addr
 * @param {mem_block_info_t} *blk
 * @param {block_stats_t} *blk_stat
 * @return {*}
 */
S32 get_block_status(void *base_addr, mem_block_info_t *blk, block_stats_t *blk_stat)
{
    mem_unit_t *mu;
    S32 alloced, used;
    alloced = used = 0;

    if (unlikely(blk == NULL || blk_stat == NULL)) {
        return -1;
    }

    for (mu = blk->mem_units; mu != NULL;) {
        alloced += mu->info.unit_size;
        if (mu->info.is_free == 0) {
            used += mu->info.unit_size;
        }

        if (mu->next == 0) {
            break;
        }
        mu = OFFSET_TO_MEM_UNIT(mu->next, base_addr);
    }

    blk_stat->alloced = alloced;
    blk_stat->used = used;

    return 0;
}